2. 继承 Inheritance
由于作者在开始本节课的学习之前已经对 Java 基本语法有一些基本的了解,该部分不会全面而详尽的叙述相关的知识,只是会简要列出一些容易混淆或忘记的知识点。
详尽的叙述可以参考 CS61B 电子课本 或 Java 教程。
在接口的定义中,我们可以通过 default 关键词给某个方法指定一个实现。这时所有继承该接口的子类无需重写该方法,当没有这样做时,其直接使用接口中已经写好的 default 方法。
我们知道可以进行如下的定义:
ParentClass obj = new ChildClass();
并且当调用 obj 的方法时,调用的是子类 ChildClass 重写的方法。这是经 Java 的 动态方法选择 机制实现的,我们来详细分析一下:
在上面的语句中。我们将 obj 声明为 ParentClass 的一个对象,这被称作 静态类型,其在变量被声明时确定,并在编译期被检查。而赋值给 obj 的对象的类型为 ChildClass,这被称作 动态类型,其在变量被赋值到某个特定对象时确定,并在运行期被检查。
当 Java 调用一个被重写的方法时,其会依据该对象的 动态类型 寻找对应的函数签名并调用它。
class Parent {
void print() {
System.out.println("Parent");
}
}
class Child extends Parent {
void print() {
System.out.println("Child");
}
}
Parent obj = new Child();
obj.print();
这段代码的输出为 Child 而非 Parent。
然而对于同一个类的多个重载方法而言,其使用静态类型寻找合适的函数签名。
void process(Object o) { System.out.println("Hi") }
void process(String s) { System.out.println(s) }
Object obj = "Hello";
process(obj);
这段代码的输出为 Hi 而非 Hello。
这样做是是因为:对于重写,其关注对象行为的 多态性,需运行时根据实际对象类型调整行为,支持灵活的代码扩展;对于重载,其关注方法参数的多样性,需在编译时明确调用逻辑,确保类型安全并消除歧义。若依赖动态类型,可能导致同一调用在不同运行时状态下绑定到不同方法,破坏代码可预测性。
总结:
- 在编译期,对变量的引用可以指向该类及其所有子类的任一实例。
- 在编译期,对方法的调用的检查依据对象的静态类型确定。
- 在运行期,对非静态方法的调用依据对象的动态类型确定。
Java 中的强制类型转换(Casting)同样是在程序的编译期起作用,即通过告知编译器去将某些对象视作指定的类型处理(前提是类型之间存在继承关系,否则会导致编译期错误)以通过编译期的类型检查。然而强制类型转换不会改变对象的值,因此可能导致运行期故障。
子类会从父类继承 除构造方法 外的所有属性(所有的静态动态属性、方法与嵌套类。然而所有子类的构造方法的第一条语句 必须为对父类构造方法的调用(无论是显式还是隐式的,即若构造方法的第一句不是父类构造方法的显式调用,Java 会自动补上一条对父类无参构造方法的调用 super())。